﻿;Gdip_All.ahk - GDI+ library compilation of user contributed GDI+ functions
;made by Marius Șucan: https://github.com/marius-sucan/AHK-GDIp-Library-Compilation
;a fork from: https://github.com/mmikeww/AHKv2-Gdip
;based on https://github.com/tariqporter/Gdip

;Features:
;   The parent class is '_GDIP', all other 'GDIP_*' inherit from it
;   Use the object method, the logic is clear
;   Simplified release, only need oInstance := "" (but pay attention to the order)
;   Encapsulate 'DC' into 'GDIP_Graphics', no need to consider the generation and release order
;       Two methods 'SelectObject' and 'UpdateLayeredWindow' are converted into 'GDIP_Graphics' methods
;       Simplified 'SelectObject': no need to record the old object, if not pass the parameter, will restore it.
;   Simplify the method call parameters, no need to pass in the properties of the object itself, for example, 'oPen' method needn't to pass in the 'pPen' parameter
;   The parameters of (x, y, w, h) are packaged and passed in in 'aRect' array instead of 4 parameters

; Important note:
;   Generally, 'oInstance := new class()' is required, for example, 'oPen' is an instance, and 'oPen.ptr' is the native 'pPen' of gdip. For the time being, oInstance only supports 1 'ptr' attribute.
;【OInstance】Used to call methods, or used as parameters by other class new (because 'oInstance.__class' can be used to determine), such as new GDIP_Graphics(oPBitmap/oHBitmap) instead of oPBitmap.ptr.
;【method】
;   The name is generally the same as the dllcall function. Those that do not involve dllcall do not start with 'Gdip'. 'DrawImage' is an exception (support oPBitmap)
;   Parameter, using pPen (compatible with native calls)
;   PS: If the parameter is filled with oPen, v2 a108+ will automatically read its ptr attribute, for example, the following two sentences are equivalent
;       oGraphics.GdipDrawEllipse(oPen,     [0,0,w,h])
;       oGraphics.GdipDrawEllipse(oPen.ptr, [0,0,w,h])

; Main class and its representation method: (used in ultisnips and positioning: for example, search pp to find the position of the GDIP_Pen class)
;主要 class 及其表示方法：(用在 ultisnips 和定位：比如搜索 pp 就能搜索到 GDIP_Pen 类的位置)
;   GDIP_PBitmap = m(NOTE)
;   GDIP_FontFamily = v(NOTE)
;   GDIP_Brush = b
;   GDIP_Font = f
;   GDIP_Graphics = g
;   GDIP_HBitmap = h
;   GDIP_Pen = p
;   GDIP_StringFormat = s

;所有 UPtr 都是由这句简化 A_PtrSize ? "UPtr" : "uint"
;特色：
;   父类是 _GDIP，其他 GDIP_* 全继承于它
;   用对象的方式，逻辑清晰
;   简化了释放，只需要 oInstance := "" 即可(但要注意顺序)
;   把 DC 封装到 GDIP_Graphics，不用考虑生成和释放顺序，使用更简单
;       两个方法 SelectObject 和 UpdateLayeredWindow 转成 GDIP_Graphics 的方法
;       简化了 SelectObject，不需要记录旧对象，不要传参即是还原
;   简化了方法调用的参数，无需传入对象本身的属性，比如 oPen的方法不需要传入 pPen 参数
;   x,y,w,h 的参数统一用 aRect 数组方式打包传入，而不是传入4个参数

; NOTE 重要说明：
;一般都需要 oInstance := new class()，比如 oPen 为实例，oPen.ptr 为 gdip 原生的 pPen。暂时 oInstance 只支持一个 ptr 属性。
;【oInstance】用来调用方法，或被其他 class new 当参数用(因为可用 oInstance.__class 判断)，比如 new GDIP_Graphics(oPBitmap/oHBitmap)，而不是用 oPBitmap.ptr。
;【方法】
;   名称，一般都是和 dllcall 函数同名，没涉及 dllcall 的则不是以 Gdip 开头，drawImage 是个例外(支持 oPBitmap)
;   参数，用的是 pPen(兼容原生调用)
;   PS：如果参数填oPen，v2 a108+会自动读取其 ptr 属性，比如以下两句等效
;   oGraphics.GdipDrawEllipse(oPen,     [0,0,w,h])
;   oGraphics.GdipDrawEllipse(oPen.ptr, [0,0,w,h])

class _GDIP {
    pToken := 0

    __new() {
        Ptr := A_PtrSize ? "UPtr" : "uint"
        pToken := 0
        if !dllcall("GetModuleHandle", "str", "gdiplus",Ptr)
            dllcall("LoadLibrary", "str", "gdiplus")
        VarSetCapacity(si, A_PtrSize == 8 ? 24 : 16, 0), si := chr(1)
        dllcall("gdiplus\GdiplusStartup",A_PtrSize ? "UPtr*" : "uint*",pToken, Ptr,&si, Ptr,0)
        this.pToken := pToken
    }

    __delete() {
        dllcall("gdiplus\GdiplusShutdown", "UPtr",this.pToken)
        if hModule := dllcall("GetModuleHandle", "str","gdiplus", "UPtr")
            dllcall("FreeLibrary", "UPtr",hModule)
        return 0
    }

    ; aRect := [100,200,(arrWH)=>0.3*arrWH[1]+20, (arrWH)=>0.4*arrWH[2]]
    ; arrWH := [300,400]
    ;aRect的每个项目都可以是函数，参数是图片的[宽，高] arrWH
    ;转成 [100, 200, 110, 160]
    rectDeal(aRect, arrWH) {
        for k, v in aRect {
            if isobject(v)
                aRect[k] := v.call(arrWH)
            else if (v < 0)
                aRect[k] := arrWH[mod(k-1,2)+1] + v ;1,3转成 w+v, 2,4转成 h+v
            else if (v < 1)
                aRect[k] := integer(arrWH[mod(k-1,2)+1] * v) ;1,3转成 w*v, 2,4转成 h*v
        }
        return aRect
    }

    ;确认鼠标选择区域
    ;aRect := _GDIP.getRect(()=>GetKeyState(RegExReplace(A_ThisLabel, ".*\s"), "P"))
    ;同 _Mouse.getRect
    getRect(funDo:="") {
        if !isobject(funDo)
            funDo := ()=>GetKeyState("LButton", "P")
        ;截图时显示的Gui
        oGui := GuiCreate("-caption +AlwaysOnTop +Border +E0x80000 +LastFound +OwnDialogs +ToolWindow")
        oGui.BackColor := "FFFFFF"
        WinSetTransparent(110)
        ;记录初始位置
        CoordMode("Mouse", "screen")
        MouseGetPos(x0, y0)
        while(funDo.call()) { ;左键按住不放
            sleep(10)
            MouseGetPos(x1, y1)
            x := min(x0, x1)
            y := min(y0, y1)
            w := abs(x0 - x1)
            h := abs(y0 - y1)
            oGui.show(format("x{1} y{2} w{3} h{4} NA", x,y,w,h))
            tooltip(format("{1}x{2}",w,h))
        }
        oGui.destroy()
        SetTimer("tooltip", -100)
        if (w<=3 || h<=3)
            exit
        return [x,y,w,h]
    }

    DeleteObject(hObject) {
        return dllcall("DeleteObject", "UPtr",hObject)
    }

    StretchBlt(ddc, aRectTo, sdc, aRectFrom, Raster:=0x00CC0020) {
        return dllcall("gdi32\StretchBlt"
            , "UPtr", ddc
            , "int", aRectTo[1],"int",aRectTo[2],"int",aRectTo[3],"int", aRectTo[4]
            , "UPtr", sdc
            , "int",aRectFrom[1],"int",aRectFrom[2],"int",aRectFrom[3],"int",aRectFrom[4]
            , "uint", Raster)
    }

    GetMonitorInfo(MonitorNum) {
        Monitors := this.MDMF_Enum()
        for k,v in Monitors
            if (v.Num = MonitorNum)
                return v
    }

    ;数组 aRect 转成 struct
    CreateRect(byref Rect, aRect) {
        VarSetCapacity(Rect, 16)
        numput(aRect[1], Rect, 0, "uint")
        numput(aRect[2], Rect, 4, "uint")
        numput(aRect[3], Rect, 8, "uint")
        numput(aRect[4], Rect, 12, "uint")
    }

    ; ======================================================================================================================
    ; Multiple Display Monitors Functions -> msdn.microsoft.com/en-us/library/dd145072(v=vs.85).aspx
    ; by 'just me'
    ; https://autohotkey.com/boards/viewtopic.php?f=6&t=4606
    ; ======================================================================================================================
    GetMonitorCount() {
        Monitors := this.MDMF_Enum()
        for k,v in Monitors
            count := A_Index
        return count
    }

    GetPrimaryMonitor() {
        Monitors := this.MDMF_Enum()
        for k,v in Monitors
            if (v.Primary)
                return v.Num
    }
    ; ======================================================================================================================
    ; Enumerates display monitors and returns an object containing the properties of all monitors or the specified monitor.
    ; ======================================================================================================================
    MDMF_Enum(HMON:="") {
        static CbFunc := (A_AhkVersion < "2") ? func("RegisterCallback") : func("CallbackCreate")
        static Monitors := {}
        this.EnumProc := CbFunc.call("MDMF_EnumProc")
        if (HMON == "") ; new enumeration
            Monitors := {}
        if (Monitors.MaxIndex() = "") ; enumerate
            if !dllcall("User32.dll\EnumDisplayMonitors", "Ptr",0, "Ptr",0, "Ptr",this.EnumProc, "Ptr",&Monitors, "uint")
                return false
        return (HMON == "") ? Monitors : Monitors.haskey(HMON) ? Monitors[HMON] : false
    }
    ; ======================================================================================================================
    ;  Callback function that is called by the MDMF_Enum function.
    ; ======================================================================================================================
    MDMF_EnumProc(HMON, hDC, PRECT, ObjectAddr) {
        Monitors := object(ObjectAddr)
        Monitors[HMON] := this.MDMF_GetInfo(HMON)
        return true
    }
    ; ======================================================================================================================
    ;  Retrieves the display monitor that has the largest area of intersection with a specified window.
    ; ======================================================================================================================
    MDMF_FromHWND(hwnd) {
        return dllcall("User32.dll\MonitorFromWindow", "Ptr",hwnd, "uint",0, "UPtr")
    }
    ; ======================================================================================================================
    ; Retrieves the display monitor that contains a specified point.
    ; if either X or Y is empty, the function will use the current cursor position for this value.
    ; ======================================================================================================================
    MDMF_FromPoint(X := "", Y := "") {
        VarSetCapacity(PT, 8, 0)
        if (X = "") || (Y = "") {
            dllcall("User32.dll\GetCursorPos", "Ptr", &PT)
            if (X = "")
                X := numget(PT, 0, "int")
            if (Y = "")
                Y := numget(PT, 4, "int")
        }
        return dllcall("User32.dll\MonitorFromPoint", "int64", (X & 0xFFFFFFFF) | (Y << 32), "uint",0, "UPtr")
    }
    ; ======================================================================================================================
    ; Retrieves the display monitor that has the largest area of intersection with a specified rectangle.
    ; Parameters are consistent with the common AHK definition of a rectangle, which is X, Y, W, H instead of
    ; left, top, right, bottom.
    ; ======================================================================================================================
    MDMF_FromRect(X, Y, W, H) {
        VarSetCapacity(RC, 16, 0)
        numput(X, RC, 0, "int"), numput(Y, RC, 4, int), numput(X + W, RC, 8, "int"), numput(Y + H, RC, 12, "int")
        return dllcall("User32.dll\MonitorFromRect", "Ptr", &RC, "uint",0, "UPtr")
    }
    ; ======================================================================================================================
    ; Retrieves information about a display monitor.
    ; ======================================================================================================================
    MDMF_GetInfo(HMON) {
        numput(VarSetCapacity(MIEX, 40 + (32 << !!A_IsUnicode)), MIEX, 0, "uint")
        if dllcall("User32.dll\GetMonitorInfo", "Ptr",HMON, "Ptr", &MIEX) {
            MonName := strget(&MIEX + 40, 32)	; CCHDEVICENAME = 32
            MonNum := RegExReplace(MonName, ".*(\d+)$", "$1")
            return {	Name:		(Name := strget(&MIEX + 40, 32))
                ,	Num:		RegExReplace(Name, ".*(\d+)$", "$1")
                ,	left:		numget(MIEX, 4, "int")		; display rectangle
                ,	top:		numget(MIEX, 8, "int")		; "
                ,	right:		numget(MIEX, 12, "int")		; "
                ,	bottom:		numget(MIEX, 16, "int")		; "
                ,	WALeft:		numget(MIEX, 20, "int")		; work area
                ,	WATop:		numget(MIEX, 24, "int")		; "
                ,	WARight:	numget(MIEX, 28, "int")		; "
                ,	WABottom:	numget(MIEX, 32, "int")		; "
                ,	Primary:	numget(MIEX, 36, "uint")}	; contains a non-zero value for the primary monitor.
        }
        return false
    }

}

;mm
class GDIP_PBitmap extends _GDIP {
    ptr := 0
    w := 0
    h := 0
    ;fp 文件存在
    ;w,h w是数字
    ;oHBitmap
    ;[x,y,w,h] aRect
    __new(w:="", h:=0) {
        if isobject(w) {
            if (w.__class == "GDIP_HBitmap")
                this.GdipCreateBitmapFromHBITMAP(hBitmap, Palette:=0)
            else if (w.length() == 4)
                this.getFromScreen(w)
        } else {
            if (w ~= "^\d+$")
                this.GdipCreateBitmapFromScan0(w, h)
            else if FileExist(w)
                this.GdipCreateBitmapFromFile(w)
            else
                msgbox(A_ThisFunc . "`n" . w . "`n" . h)
        }
    }

    ;出错，可排查释放顺序
    __delete() {
        ; msgbox(A_ThisFunc . '---')
        dllcall("gdiplus\GdipDisposeImage", "UPtr",this.ptr)
        return
    }

    GdipCreateBitmapFromScan0(w, h, PixelFormat:=0x26200A) {
        dllcall("gdiplus\GdipCreateBitmapFromScan0", "int",w, "int",h, "int",0, "int",PixelFormat, "UPtr",0, "UPtr*",pBitmap)
        if !pBitmap {
            msgbox(A_ThisFunc . "`n" . w . "`n" . h)
        }
        this.w := w
        this.h := h
        this.ptr := pBitmap
    }

    ; GdipCreateBitmapFromFile(fp, IconNumber:=1, IconSize:="")
    GdipCreateBitmapFromFile(fp) {
        dllcall("gdiplus\GdipCreateBitmapFromFile", "UPtr",&fp, "UPtr*",pBitmap)
        if (!pBitmap)
            msgbox(A_ThisFunc . "`n" . fp)
        this.w := 0
        this.h := 0
        this.ptr := pBitmap
        ; SplitPath(fp,,, ext)
        ; if RegExMatch(ext, "^(i:exe|dll)$") {
        ;     Sizes := IconSize ? IconSize : 256 "|" 128 "|" 64 "|" 48 "|" 32 "|" 16
        ;     BufSize := 16 + (2*A_PtrSize)
        ;     VarSetCapacity(buf, BufSize, 0)
        ;     for eachSize, Size in StrSplit(Sizes, "|") {
        ;         dllcall("PrivateExtractIcons", "str",fp, "int",IconNumber-1, "int",Size, "int",Size, "UPtr*",hIcon, "UPtr*",0, "uint",1, "uint",0)
        ;         if !hIcon
        ;             continue
        ;         if !dllcall("GetIconInfo", "UPtr",hIcon, "UPtr",&buf) {
        ;             DestroyIcon(hIcon)
        ;             continue
        ;         }
        ;         hbmMask  := numget(buf, 12 + (A_PtrSize - 4))
        ;         hbmColor := numget(buf, 12 + (A_PtrSize - 4) + A_PtrSize)
        ;         if !(hbmColor && dllcall("GetObject", "UPtr",hbmColor, "int",BufSize, "UPtr",&buf)) {
        ;             DestroyIcon(hIcon)
        ;             continue
        ;         }
        ;         break
        ;     }
        ;     if !hIcon
        ;         return -1
        ;     width := numget(buf, 4, "int"), height := numget(buf, 8, "int")
        ;     hbm := CreateDIBSection(width, -height)
        ;     hDC := GDIP_DC.create()
        ;     obm := SelectObject(hDC, hbm)
        ;     if !dllcall("DrawIconEx", "UPtr",hDC, "int",0, "int",0, "UPtr",hIcon, "uint",width, "uint",height, "uint",0, "UPtr",0, "uint",3) {
        ;         DestroyIcon(hIcon)
        ;         return -2
        ;     }
        ;     VarSetCapacity(dib, 104)
        ;     ; sizeof(DIBSECTION) = 76+2*(A_PtrSize=8?4:0)+2*A_PtrSize
        ;     dllcall("GetObject", "UPtr",hbm, "int",A_PtrSize == 8 ? 104 : 84, "UPtr",&dib)
        ;     Stride := numget(dib, 12, "int"), Bits := numget(dib, 20 + (A_PtrSize - 4)) ; padding
        ;     dllcall("gdiplus\GdipCreateBitmapFromScan0", "int",width, "int",height, "int",Stride, "int",0x26200A, "UPtr",Bits, "UPtr*",pBitmapOld)
        ;     pBitmap := Gdip_CreateBitmap(width, height)
        ;     _G := Gdip_GraphicsFromImage(pBitmap)
        ;         , Gdip_DrawImage(_G, pBitmapOld, 0, 0, width, height, 0, 0, width, height)
        ;     SelectObject(hDC, obm), dllcall("gdi32\DeleteObject", "UPtr",hbm), DeleteDC(hDC)
        ;     Gdip_DeleteGraphics(_G), Gdip_DisposeImage(pBitmapOld)
        ;     DestroyIcon(hIcon)
        ;     return this.ptr := pBitmap
        ; }
    }

    ; GdipCreateBitmapFromFileICM(bitmap) {
    ;     dllcall("gdiplus\GdipCreateBitmapFromFileICM", "wstr",bitmap, "ptr*",pBitmap)
    ;     if (!pBitmap)
    ;         msgbox(A_ThisFunc)
    ;     return this.ptr := pBitmap
    ; }

    getFromScreen(aRect, Raster:="") {
        this.GdipCreateBitmapFromHBITMAP(new GDIP_HBitmap(aRect))
    }

    GdipCreateBitmapFromHBITMAP(oHBitmap, Palette:=0) {
        dllcall("gdiplus\GdipCreateBitmapFromHBITMAP", "UPtr",oHBitmap.ptr, "UPtr",Palette, "UPtr*",pBitmap)
        return this.ptr := pBitmap
    }

    ; getFromClipboard() {
    ;     if !dllcall("IsClipboardFormatAvailable", "uint",8)
    ;         return -2
    ;     if !dllcall("OpenClipboard", "UPtr",0)
    ;         return -1
    ;     if !hBitmap := dllcall("GetClipboardData", "uint",2, "UPtr")
    ;         return -3
    ;     if !pBitmap := this.GdipCreateBitmapFromHBITMAP(oHBitmap)
    ;         return -4
    ;     if !dllcall("CloseClipboard")
    ;         return -5
    ;     dllcall("gdi32\DeleteObject", "UPtr",hBitmap)
    ;     return this.ptr := pBitmap
    ; }

    ; getFromHICON(hIcon) {
    ;     dllcall("gdiplus\GdipCreateBitmapFromHICON",A_PtrSize ? "UPtr" : "uint",hIcon, A_PtrSize ? "UPtr*" : "uint*",pBitmap)
    ;     return this.ptr := pBitmap
    ; }

    getSize(byref w, byref h) {
        if !this.w {
            dllcall("gdiplus\GdipGetImageWidth", "UPtr",this.ptr, "uint*",w)
            this.w := w
        }
        if !this.h {
            dllcall("gdiplus\GdipGetImageHeight", "UPtr",this.ptr, "uint*",h)
            this.h := h
        }
        return [this.w, this.h]
    }
    getWidth() {
        if !this.w {
            dllcall("gdiplus\GdipGetImageWidth", "UPtr",this.ptr, "uint*",w)
            this.w := w
        }
        return this.w
    }
    getHeight() {
        if !this.h {
            dllcall("gdiplus\GdipGetImageHeight", "UPtr",this.ptr, "uint*",h)
            this.h := h
        }
        return this.h
    }

    getPixel(x, y) {
        dllcall("gdiplus\GdipBitmapGetPixel", "Ptr",this.ptr, "int",x, "int",y, "Uint*",ARGB:=0)
        return ARGB
    }
    setPixel(x, y, ARGB) {
        return dllcall("gdiplus\GdipBitmapSetPixel", "Ptr",this.ptr, "int",x, "int",y, "int",ARGB)
    }

    ; TODO
    ; https://autohotkey.com/board/topic/29449-gdi-standard-library-145-by-tic/page-58#entry455137
    rotate(angle) {
        this.getSize(w, h)
        aRect := GDIP_Graphics.getRotatedRect(w, h, angle)
        xOffset := aRect[1]
        yOffset := aRect[2]
        wNew := aRect[3]
        hNew := aRect[4]
        oPBitmap := new GDIP_PBitmap(wNew, hNew)
        oGraphics := new GDIP_Graphics(oPBitmap)
        oGraphics.GdipSetInterpolationMode(7)
        oGraphics.GdipSetSmoothingMode(4)
        oGraphics.GdipDrawImageRectRect(this.ptr, [0,0,wNew,hNew], [0,0,wNew,hNew])
        oGraphics := ""
        this.ptr := oPBitmap.ptr
        oPBitmap := ""
        return this.ptr
    }

    ;剪切
    crop(left:=0, right:=0, up:=0, down:=0, Dispose:=1) {
        this.getSize(w, h)
        wNew := w-left-right
        hNew := h-up-down
        oPBitmap := new GDIP_PBitmap(wNew, hNew)
        oGraphics := new GDIP_Graphics(oPBitmap)
        oGraphics.GdipSetInterpolationMode(7)
        oGraphics.GdipSetSmoothingMode(4)
        oGraphics.GdipDrawImageRectRect(this.ptr, [0,0,wNew,hNew], [left,up,wNew,hNew])
        oGraphics := ""
        this.ptr := oPBitmap.ptr
        oPBitmap := ""
        return this.ptr
    }

    ; http://www.autohotkey.com/community/viewtopic.php?p=477333
    ; returns resized bitmap. By Learning one.
    resize(PercentOrWH, Dispose:=1) {
        this.getSize(w, h)
        if isobject(PercentOrWH) {
            wNew := PercentOrWH[1]
            hNew := PercentOrWH[2]
        } else {
            wNew := w*PercentOrWH/100
            hNew := h*PercentOrWH/100		
        }
        oPBitmap := new GDIP_PBitmap(wNew, hNew)
        oGraphics := new GDIP_Graphics(oPBitmap)
        oGraphics.GdipSetInterpolationMode(7)
        oGraphics.GdipSetSmoothingMode(4)
        oGraphics.GdipDrawImageRectRect(this.ptr, [0,0,wNew,hNew], [0,0,w,h])
        oGraphics := ""
        this.ptr := oPBitmap.ptr
        if Dispose
            oPBitmap := ""
        return this.ptr
    }

    ;FIXME 马赛克
    ; GdipPixelateBitmap(pBitmap, oBitmapOut, BlockSize) {
    ;     if (!PixelateBitmap) {
    ;         if A_PtrSize != 8 {
    ;             MCode_PixelateBitmap := "
    ;             (ltrim join
    ;             558BEC83EC3C8B4514538B5D1C99F7FB56578BC88955EC894DD885C90F8E830200008B451099F7FB8365DC008365E000894DC88955F08945E833FF897DD4
    ;             397DE80F8E160100008BCB0FAFCB894DCC33C08945F88945FC89451C8945143BD87E608B45088D50028BC82BCA8BF02BF2418945F48B45E02955F4894DC4
    ;             8D0CB80FAFCB03CA895DD08BD1895DE40FB64416030145140FB60201451C8B45C40FB604100145FC8B45F40FB604020145F883C204FF4DE475D6034D18FF
    ;             4DD075C98B4DCC8B451499F7F98945148B451C99F7F989451C8B45FC99F7F98945FC8B45F899F7F98945F885DB7E648B450C8D50028BC82BCA83C103894D
    ;             C48BC82BCA41894DF48B4DD48945E48B45E02955E48D0C880FAFCB03CA895DD08BD18BF38A45148B7DC48804178A451C8B7DF488028A45FC8804178A45F8
    ;             8B7DE488043A83C2044E75DA034D18FF4DD075CE8B4DCC8B7DD447897DD43B7DE80F8CF2FEFFFF837DF0000F842C01000033C08945F88945FC89451C8945
    ;             148945E43BD87E65837DF0007E578B4DDC034DE48B75E80FAF4D180FAFF38B45088D500203CA8D0CB18BF08BF88945F48B45F02BF22BFA2955F48945CC0F
    ;             B6440E030145140FB60101451C0FB6440F010145FC8B45F40FB604010145F883C104FF4DCC75D8FF45E4395DE47C9B8B4DF00FAFCB85C9740B8B451499F7
    ;             F9894514EB048365140033F63BCE740B8B451C99F7F989451CEB0389751C3BCE740B8B45FC99F7F98945FCEB038975FC3BCE740B8B45F899F7F98945F8EB
    ;             038975F88975E43BDE7E5A837DF0007E4C8B4DDC034DE48B75E80FAF4D180FAFF38B450C8D500203CA8D0CB18BF08BF82BF22BFA2BC28B55F08955CC8A55
    ;             1488540E038A551C88118A55FC88540F018A55F888140183C104FF4DCC75DFFF45E4395DE47CA68B45180145E0015DDCFF4DC80F8594FDFFFF8B451099F7
    ;             FB8955F08945E885C00F8E450100008B45EC0FAFC38365DC008945D48B45E88945CC33C08945F88945FC89451C8945148945103945EC7E6085DB7E518B4D
    ;             D88B45080FAFCB034D108D50020FAF4D18034DDC8BF08BF88945F403CA2BF22BFA2955F4895DC80FB6440E030145140FB60101451C0FB6440F010145FC8B
    ;             45F40FB604080145F883C104FF4DC875D8FF45108B45103B45EC7CA08B4DD485C9740B8B451499F7F9894514EB048365140033F63BCE740B8B451C99F7F9
    ;             89451CEB0389751C3BCE740B8B45FC99F7F98945FCEB038975FC3BCE740B8B45F899F7F98945F8EB038975F88975103975EC7E5585DB7E468B4DD88B450C
    ;             0FAFCB034D108D50020FAF4D18034DDC8BF08BF803CA2BF22BFA2BC2895DC88A551488540E038A551C88118A55FC88540F018A55F888140183C104FF4DC8
    ;             75DFFF45108B45103B45EC7CAB8BC3C1E0020145DCFF4DCC0F85CEFEFFFF8B4DEC33C08945F88945FC89451C8945148945103BC87E6C3945F07E5C8B4DD8
    ;             8B75E80FAFCB034D100FAFF30FAF4D188B45088D500203CA8D0CB18BF08BF88945F48B45F02BF22BFA2955F48945C80FB6440E030145140FB60101451C0F
    ;             B6440F010145FC8B45F40FB604010145F883C104FF4DC875D833C0FF45108B4DEC394D107C940FAF4DF03BC874068B451499F7F933F68945143BCE740B8B
    ;             451C99F7F989451CEB0389751C3BCE740B8B45FC99F7F98945FCEB038975FC3BCE740B8B45F899F7F98945F8EB038975F88975083975EC7E63EB0233F639
    ;             75F07E4F8B4DD88B75E80FAFCB034D080FAFF30FAF4D188B450C8D500203CA8D0CB18BF08BF82BF22BFA2BC28B55F08955108A551488540E038A551C8811
    ;             8A55FC88540F018A55F888140883C104FF4D1075DFFF45088B45083B45EC7C9F5F5E33C05BC9C21800
    ;             )"
    ;         } else {
    ;             MCode_PixelateBitmap := "
    ;             (ltrim join
    ;             4489442418488954241048894C24085355565741544155415641574883EC28418BC1448B8C24980000004C8BDA99488BD941F7F9448BD0448BFA8954240C
    ;             448994248800000085C00F8E9D020000418BC04533E4458BF299448924244C8954241041F7F933C9898C24980000008BEA89542404448BE889442408EB05
    ;             4C8B5C24784585ED0F8E1A010000458BF1418BFD48897C2418450FAFF14533D233F633ED4533E44533ED4585C97E5B4C63BC2490000000418D040A410FAF
    ;             C148984C8D441802498BD9498BD04D8BD90FB642010FB64AFF4403E80FB60203E90FB64AFE4883C2044403E003F149FFCB75DE4D03C748FFCB75D0488B7C
    ;             24188B8C24980000004C8B5C2478418BC59941F7FE448BE8418BC49941F7FE448BE08BC59941F7FE8BE88BC69941F7FE8BF04585C97E4048639C24900000
    ;             004103CA4D8BC1410FAFC94863C94A8D541902488BCA498BC144886901448821408869FF408871FE4883C10448FFC875E84803D349FFC875DA8B8C249800
    ;             0000488B5C24704C8B5C24784183C20448FFCF48897C24180F850AFFFFFF8B6C2404448B2424448B6C24084C8B74241085ED0F840A01000033FF33DB4533
    ;             DB4533D24533C04585C97E53488B74247085ED7E42438D0C04418BC50FAF8C2490000000410FAFC18D04814863C8488D5431028BCD0FB642014403D00FB6
    ;             024883C2044403D80FB642FB03D80FB642FA03F848FFC975DE41FFC0453BC17CB28BCD410FAFC985C9740A418BC299F7F98BF0EB0233F685C9740B418BC3
    ;             99F7F9448BD8EB034533DB85C9740A8BC399F7F9448BD0EB034533D285C9740A8BC799F7F9448BC0EB034533C033D24585C97E4D4C8B74247885ED7E3841
    ;             8D0C14418BC50FAF8C2490000000410FAFC18D04814863C84A8D4431028BCD40887001448818448850FF448840FE4883C00448FFC975E8FFC2413BD17CBD
    ;             4C8B7424108B8C2498000000038C2490000000488B5C24704503E149FFCE44892424898C24980000004C897424100F859EFDFFFF448B7C240C448B842480
    ;             000000418BC09941F7F98BE8448BEA89942498000000896C240C85C00F8E3B010000448BAC2488000000418BCF448BF5410FAFC9898C248000000033FF33
    ;             ED33F64533DB4533D24533C04585FF7E524585C97E40418BC5410FAFC14103C00FAF84249000000003C74898488D541802498BD90FB642014403D00FB602
    ;             4883C2044403D80FB642FB03F00FB642FA03E848FFCB75DE488B5C247041FFC0453BC77CAE85C9740B418BC299F7F9448BE0EB034533E485C9740A418BC3
    ;             99F7F98BD8EB0233DB85C9740A8BC699F7F9448BD8EB034533DB85C9740A8BC599F7F9448BD0EB034533D24533C04585FF7E4E488B4C24784585C97E3541
    ;             8BC5410FAFC14103C00FAF84249000000003C74898488D540802498BC144886201881A44885AFF448852FE4883C20448FFC875E941FFC0453BC77CBE8B8C
    ;             2480000000488B5C2470418BC1C1E00203F849FFCE0F85ECFEFFFF448BAC24980000008B6C240C448BA4248800000033FF33DB4533DB4533D24533C04585
    ;             FF7E5A488B7424704585ED7E48418BCC8BC5410FAFC94103C80FAF8C2490000000410FAFC18D04814863C8488D543102418BCD0FB642014403D00FB60248
    ;             83C2044403D80FB642FB03D80FB642FA03F848FFC975DE41FFC0453BC77CAB418BCF410FAFCD85C9740A418BC299F7F98BF0EB0233F685C9740B418BC399
    ;             F7F9448BD8EB034533DB85C9740A8BC399F7F9448BD0EB034533D285C9740A8BC799F7F9448BC0EB034533C033D24585FF7E4E4585ED7E42418BCC8BC541
    ;             0FAFC903CA0FAF8C2490000000410FAFC18D04814863C8488B442478488D440102418BCD40887001448818448850FF448840FE4883C00448FFC975E8FFC2
    ;             413BD77CB233C04883C428415F415E415D415C5F5E5D5BC3
    ;             )"
    ;         }
    ;         VarSetCapacity(PixelateBitmap, strlen(MCode_PixelateBitmap)//2)
    ;         loop(strlen(MCode_PixelateBitmap)//2)
    ;             NumPut("0x" substr(MCode_PixelateBitmap, (2*A_Index)-1, 2), PixelateBitmap, A_Index-1, "uchar")
    ;         dllcall("VirtualProtect", "UPtr",&PixelateBitmap, "UPtr",VarSetCapacity(PixelateBitmap), "uint",0x40, "UPtr*",0)
    ;     }
    ;     this.getSize(Width, Height)
    ;     E1 := this.GdipBitmapLockBits([0, 0, Width, Height], Stride1, Scan01, BitmapData1)
    ;     E2 := oBitmapOut.GdipBitmapLockBits([0, 0, Width, Height], Stride2, Scan02, BitmapData2)
    ;     if (E1 || E2)
    ;         return -3
    ;     ; E := - unused exit code
    ;     dllcall(&PixelateBitmap, "UPtr",Scan01, "UPtr",Scan02, "int",Width, "int",Height, "int",Stride1, "int",BlockSize)
    ;     this.GdipBitmapUnlockBits(BitmapData1)
    ;     oBitmapOut.GdipBitmapUnlockBits(BitmapData2)
    ;     return 0
    ; }

    ;Quality 0-100
    GdipSaveImageToFile(fp, Quality:=75) {
        SplitPath(fp,,, ext)
        if !(ext ~= "^(?i:bmp|dib|rle|jpg|jpeg|jpe|jfif|gif|tif|tiff|png)$")
            return -1
        ext := "." . ext
        dllcall("gdiplus\GdipGetImageEncodersSize", "uint*",nCount, "uint*",nSize)
        VarSetCapacity(ci, nSize)
        dllcall("gdiplus\GdipGetImageEncoders", "uint",nCount, "uint",nSize, "UPtr", &ci)
        if !(nCount && nSize)
            return -2
        loop(nCount) {
            sString := strget(numget(ci, (idx := (48+7*A_PtrSize)*(A_Index-1))+32+3*A_PtrSize), "UTF-16")
            if !instr(sString, "*" . ext)
                continue
            pCodec := &ci+idx
            break
        }
        if !pCodec
            return -3
        pQuality := 0
        if (Quality != 75) {
            Quality := (Quality < 0) ? 0 : (Quality > 100) ? 100 : Quality
            if (ext ~= "^\.(?i:JPG|JPEG|JPE|JFIF)$") {
                dllcall("gdiplus\GdipGetEncoderParameterListSize","UPtr",this.ptr, "UPtr",pCodec, "uint*",nSize)
                VarSetCapacity(EncoderParameters, nSize, 0)
                dllcall("gdiplus\GdipGetEncoderParameterList","UPtr",this.ptr, "UPtr",pCodec, "uint",nSize, "UPtr",&EncoderParameters)
                loop(numget(EncoderParameters, "uint")) {
                    elem := (24+(A_PtrSize ? A_PtrSize : 4)) * (A_Index-1) + 4 + (pad := A_PtrSize-4)
                    if (numget(EncoderParameters, elem+16, "uint") == 1) && (numget(EncoderParameters, elem+20, "uint") == 6) {
                        pQuality := elem + &EncoderParameters - pad - 4
                        numput(Quality, numget(numput(4, numput(1, pQuality+0)+20, "uint")), "uint")
                        break
                    }
                }
            }
        }
        _E := dllcall("gdiplus\GdipSaveImageToFile","UPtr",this.ptr, "UPtr",&fp, "UPtr",pCodec, "uint",pQuality)
        return _E ? -5 : 0
    }

    ; https://docs.microsoft.com/en-us/windows/win32/api/gdiplusheaders/nf-gdiplusheaders-bitmap-lockbits
    ; LockMode https://docs.microsoft.com/en-us/dotnet/api/system.drawing.imaging.imagelockmode
    ;       ReadOnly	1	
    ;       WriteOnly	2	
    ;       ReadWrite	3	
    ;       UserInputBuffer	4	
    ; PixelFormat https://docs.microsoft.com/en-us/dotnet/api/system.drawing.imaging.pixelformat
    ;       Argb	0x26200A
    ;       rgb		？不是0x21808
    GdipBitmapLockBits(aRect, ByRef Stride, ByRef Scan0, ByRef BitmapData, LockMode:=3, PixelFormat:=0x21808) {
        VarSetCapacity(_Rect, A_PtrSize*4)
        numput(aRect[1], _Rect, A_PtrSize*0, "uint")
        numput(aRect[2], _Rect, A_PtrSize*1, "uint")
        numput(aRect[3], _Rect, A_PtrSize*2, "uint")
        numput(aRect[4], _Rect, A_PtrSize*3, "uint")
        VarSetCapacity(BitmapData, A_PtrSize*6, 0)
        _E := dllcall("Gdiplus\GdipBitmapLockBits", "UPtr",this.ptr, "UPtr",&_Rect, "uint",LockMode, "int",PixelFormat, "UPtr",&BitmapData)
        Stride := NumGet(BitmapData, 8, "Int")
        Scan0 := NumGet(BitmapData, 16, "UPtr")
        return _E
    }

    ;获取的可能是 agrb
    getLockBitPixel(Scan0, x, y, Stride) {
        return NumGet(Scan0, (x*4)+(y*Stride), "UInt")
    }

    GdipBitmapUnlockBits(ByRef BitmapData) {
        return dllcall("Gdiplus\GdipBitmapUnlockBits", "UPtr",this.ptr, "UPtr",&BitmapData)
    }

}

;hh
;需要用 Gui 显示的，需要 oHBitmap 并生成 oGraphics
class GDIP_HBitmap extends _GDIP {
    ptr := 0

    __new(widthOrPBitmap, heightOrColor:=0xffFFFFFF, hDC:=0) {
        if isobject(widthOrPBitmap) {
            if (widthOrPBitmap.__class == "GDIP_PBitmap") {
                dllcall("gdiplus\GdipCreateHBITMAPFromBitmap", "UPtr",widthOrPBitmap.ptr, "UPtr*",hBitmap, "int",heightOrColor)
                this.ptr := hBitmap
            } else if (widthOrPBitmap.length() == 4)
                this.getFromScreen(widthOrPBitmap)
        } else
            this.CreateDIBSection(widthOrPBitmap, heightOrColor, hDC)
    }

    __delete() {
        ; msgbox(A_ThisFunc . '---')
        base.DeleteObject(this.ptr)
    }

    getFromScreen(aRect:=0) {
        x := aRect[1]
        y := aRect[2]
        w := aRect[3]
        h := aRect[4]
        ;方式1，原始
        tDC := dllcall("CreateCompatibleDC", "Uint", 0)
        ;创建 hBitmap
        this.CreateDIBSection(w, h, tDC)
        ;修改 hBitmap
        oBM := dllcall("SelectObject", "Uint", tDC, "Uint", this.ptr)
        hDC := dllcall("GetDC", "Uint", 0)
        dllcall("BitBlt", "Uint", tDC, "int",0, "int",0, "int",w, "int",h, "Uint",hDC, "int",x, "int",y, "Uint",0x40000000 | 0x00CC0020)
        dllcall("ReleaseDC", "Uint", 0, "Uint", hDC)
        dllcall("SelectObject", "Uint",tDC, "Uint",oBM)
        dllcall("DeleteDC", "Uint", tDC)
    }

    CreateDIBSection(w, h, hDC:=0, bpp:=32, ByRef ppvBits:=0) {
        hDC2 := hDC ? hDC : dllcall("GetDC", "UPtr",0)
        VarSetCapacity(bi, 40, 0)
        numput(w, bi, 4, "uint")
        numput(h, bi, 8, "uint")
        numput(40, bi, 0, "uint")
        numput(1, bi, 12, "ushort")
        numput(0, bi, 16, "uint")
        numput(bpp, bi, 14, "ushort")
        hBitmap := dllcall("CreateDIBSection", "ptr",hDC2, "ptr",&bi, "uint",0, "UPtr*",ppvBits, "ptr",0, "uint",0, "UPtr")
        if !hDC
            dllcall("ReleaseDC", "UPtr",0, "UPtr",hDC2)
        if (!hBitmap)
            msgbox(A_ThisFunc . "`n" . w . "`n" . h)
        return this.ptr := hBitmap
    }

    ; Gdip_CreateHICONFromBitmap(pBitmap) {
    ;     pBitmap := ""
    ;     hIcon := 0
    ;     dllcall("gdiplus\GdipCreateHICONFromBitmap", "UPtr",pBitmap, "UPtr*",hIcon)
    ;     return hIcon
    ; }

    ; SetImage(ctlID) {
    ;     res := SendMessage(0x172,, this.ptr,, "ahk_id " . ctlID)
    ;     dllcall("DeleteObject", "Uint",res)
    ;     return res
    ; }

    ;复制 hBitmap 图像到剪切板
    SetClipboardData(bDelete:=false) {
        dllcall("GetObject", "Uint",this.ptr, "int",VarSetCapacity(oi,84,0), "Uint",&oi)
        hDIB := dllcall("GlobalAlloc", "Uint",2, "Uint",40+NumGet(oi,44))
        pDIB := dllcall("GlobalLock", "Uint",hDIB)
        dllcall("RtlMoveMemory", "Uint",pDIB, "Uint",&oi+24, "Uint",40)
        dllcall("RtlMoveMemory", "Uint",pDIB+40, "Uint",NumGet(oi,20), "Uint",NumGet(oi,44))
        dllcall("GlobalUnlock", "Uint",hDIB)
        if bDelete
            dllcall("DeleteObject", "Uint",this.ptr)
        dllcall("OpenClipboard", "Uint",0)
        dllcall("EmptyClipboard")
        dllcall("SetClipboardData", "Uint",8, "Uint",hDIB)
        dllcall("CloseClipboard")
    }

    showByGui(aRect) {
        ;放入gui
        oGui := GuiCreate("-Caption +ToolWindow +AlwaysOnTop +LastFound +Border -DPIScale")
        ;oGui.title := "hyd-" . A_Now
        oGui.OnEvent("ContextMenu", ()=>oGui.destroy())
        oGui.MarginX := 0
        oGui.MarginY := 0
        oPic := oGui.Addpicture(format("w{1} h{2} +0xE", aRect[3],aRect[4]))
        oPic.OnEvent("click", ()=>PostMessage(0xA1, 2)) ;WM_NCLBUTTONDOWN 随着鼠标移动
        oPic.OnEvent("DoubleClick", ObjBindMethod(this,"zoom"))
        SendMessage(STM_SETIMAGE:=0x172,, this.ptr,, "ahk_id " . oPic.hwnd)
        oGui.show(format("x{1} y{2}", aRect[1]-1,aRect[2]-1))
    }
    zoom(oCtl) {
        n := 2
        objPos := oCtl.pos
        objGuiPos := oCtl.Gui.pos
        w := objPos.w
        h := objPos.h
        oGdip := new _GDIP()
        oPBitmap := new GDIP_PBitmap([objGuiPos.x+1,objGuiPos.y+1,w,h])
        this.resize(n*100)
        oHBitmap := new GDIP_HBitmap(oPBitmap)
        oPBitmap := ""
        SendMessage(STM_SETIMAGE:=0x172,, this.ptr,, "ahk_id " . oCtl.hwnd)
        ControlMove(,,w*n,h*n, oCtl)
        WinMove(,, w*n, h*n, "ahk_id " . oCtl.Gui.hwnd)
        ;if !isobject(oCtl)
        ;{
        ;MouseGetPos(,, idWin, oCtl)
        ;ControlGetPos(x,y,w,h, oCtl, "ahk_id " . idWin)
        ;}
        ;else
        ;{
        ;objPos := oCtl.pos
        ;w := objPos.w
        ;h := objPos.h
        ;objGuiPos := oCtl.Gui.pos
        ;x := objGuiPos.x+1
        ;y := objGuiPos.y+1
        ;}
        ;pToken  := Gdip_Startup()
        ;pBitmap := Gdip_BitmapFromScreen(format("{1}|{2}|{3}|{4}", x,y,w,h))
        ;pBitmap := Gdip_ResizeBitmap(pBitmap, 200)
        ;hBitmap := Gdip_CreateHBITMAPFromBitmap(pBitmap)
        ;Gdip_DisposeImage(pBitmap)
        ;Gdip_Shutdown(pToken)
        ;E := SendMessage(0x172, 0, hBitmap,, "ahk_id " . oCtl)
        ;;E := SendMessage(0x172, 0, hBitmap,, "ahk_id " . oCtl.hwnd)
        ;DllCall("DeleteObject", "UPtr",E)
        ;ControlMove(,,w*2,h*2, oCtl)
        ;WinMove(,,w*2, h*2,"ahk_id " . oCtl.Gui.hwnd)
    }
}

;gg
;NOTE 写内容都通过 GDIP_Graphics，写好后，相应的 GDIP_PBitmap就已修改，可直接 GdipSaveImageToFile
class GDIP_Graphics extends _GDIP {
    ptr := 0
    hDC := 0

    __new(oInstance) {
        if isobject(oInstance) {
            if (oInstance.__class == "GDIP_PBitmap")
                this.GdipGetImageGraphicsContext(oInstance)
            else if (oInstance.__class == "GDIP_HBitmap") { ;NOTE 先生成 this.hDC
                this.hDC := dllcall("CreateCompatibleDC", "UPtr",0)
                this.SelectObject(oInstance.ptr) ;NOTE 必须先运行
                res := dllcall("gdiplus\GdipCreateFromHDC", "UPtr",this.hDC, "UPtr*",pGraphics)
                if (!pGraphics)
                    msgbox(A_ThisFunc . "`n" . res . "`n" . oInstance)
                this.ptr := pGraphics
            }
        }
    }

    __delete() {
        if this.hDC
            dllcall("DeleteDC", "UPtr",this.hDC)
        dllcall("gdiplus\GdipDeleteGraphics", "Ptr",this.ptr)
    }

    GdipGetImageGraphicsContext(oPBitmap) {
        dllcall("gdiplus\GdipGetImageGraphicsContext",A_PtrSize ? "UPtr" : "uint",oPBitmap.ptr, A_PtrSize ? "UPtr*" : "uint*",pGraphics)
        if (!pGraphics)
            msgbox(A_ThisFunc)
        this.ptr := pGraphics
    }

    GdipGraphicsClear(color := 0) {
        return dllcall("gdiplus\GdipGraphicsClear", "Ptr",this.ptr, "uint",color)
    }

    GdipResetClip() {
        dllcall("gdiplus\GdipResetClip", "UPtr",this.ptr)
    }

    ; default = 0
    ; HighSpeed = 1
    ; HighQuality = 2
    ; None = 3
    ; AntiAlias = 4 边缘平滑
    GdipSetSmoothingMode(smoothingMode:=4) {
        return dllcall("gdiplus\GdipSetSmoothingMode", "Ptr",this.ptr, "int",smoothingMode)
    }

    ; default = 0
    ; LowQuality = 1
    ; HighQuality = 2
    ; Bilinear = 3
    ; Bicubic = 4
    ; NearestNeighbor = 5
    ; HighQualityBilinear = 6
    ; HighQualityBicubic = 7
    GdipSetInterpolationMode(interpolationMode:=7) {
        return dllcall("gdiplus\GdipSetInterpolationMode", "Ptr",this.ptr, "int",interpolationMode)
    }

    ;TextRenderingHintSystemDefault              = 0,
    ;TextRenderingHintSingleBitPerPixelGridFit   = 1,
    ;TextRenderingHintSingleBitPerPixel          = 2,
    ;TextRenderingHintAntiAliasGridFit           = 3,
    ;TextRenderingHintAntiAlias                  = 4,
    ;TextRenderingHintClearTypeGridFit           = 5
    GdipSetTextRenderingHint(TextRenderingHint:=0) {
        return dllcall("gdiplus\GdipSetTextRenderingHint", "Ptr",this.ptr, "uint",textRenderingHint)
    }

    ;------------------------------------------------rotate------------------------------------------------
    ;旋转相关
    ;根据【左上角】旋转，新的画布宽高会调整为最小矩形
    ;是给 GdipDrawImageRectRect 等【绘制】工作指定参数
    ;   1.getRotatedRect 旋转后的左上角坐标-原坐标【差值】和【新的宽高】

    ;旋转后新的宽高
    ;原左上角坐标相对新左上角坐标的偏移：xOffset, yOffset
    ;后续
    ;   oGraphics.GdipTranslateWorldTransform(xOffset, yOffset)
    getRotatedRect(w, h, angle) {
        pi := 3.14159
        TAngle := angle*(pi/180)
        bound := (angle >= 0) ? mod(angle, 360) : 360-mod(-angle, -360)
        if (bound <= 90) {
            xOffset := h*sin(TAngle)
            yOffset := 0
        } else if (bound <= 180) {
            xOffset := (h*sin(TAngle))-(w*cos(TAngle))
            yOffset := -h*cos(TAngle)
        } else if (bound <= 270) {
            xOffset := -w*cos(TAngle)
            yOffset := -(h*cos(TAngle))-(w*sin(TAngle))
        } else {
            xOffset := 0
            yOffset := -w*sin(TAngle)
        }
        newW := ceil(abs(w*cos(TAngle))+abs(h*sin(TAngle)))
        newH := ceil(abs(w*sin(TAngle))+abs(h*cos(Tangle)))
        return [xOffset, yOffset, newW, newH]
    }

    ;翻转
    ;水平翻转(以右线翻转)
    ;   GdipScaleWorldTransform(-1, 1)
    ;   GdipTranslateWorldTransform(-w, 0)
    ;垂直翻转(以下线翻转)
    ;   GdipScaleWorldTransform(1, -1)
    ;   GdipTranslateWorldTransform(0, -h)
    GdipScaleWorldTransform(xScale, yScale, MatrixOrder:=0) {
        return dllcall("gdiplus\GdipScaleWorldTransform", "Ptr",this.ptr, "float",xScale, "float",yScale, "int",MatrixOrder)
    }

    ;偏移坐标(新-旧)
    ;NOTE 要在 GdipRotateWorldTransform 之后运行
    GdipTranslateWorldTransform(xOffset, yOffset, MatrixOrder:=0) {
        return dllcall("gdiplus\GdipTranslateWorldTransform", "Ptr",this.ptr, "float",xOffset, "float",yOffset, "int",MatrixOrder)
    }

    ;回收
    GdipResetWorldTransform() {
        return dllcall("gdiplus\GdipResetWorldTransform", "Ptr",this.ptr)
    }

    ;旋转 angle 度
    ;TODO MatrixOrder = 0; The new operation is applied before the old operation.
    ; MatrixOrder = 1; The new operation is applied after the old operation.
    ;NOTE 要在 GdipTranslateWorldTransform 等调整好之后再运行
    GdipRotateWorldTransform(angle:=90, MatrixOrder:=0) {
        return dllcall("gdiplus\GdipRotateWorldTransform", "Ptr",this.ptr, "float",angle, "int",MatrixOrder)
    }

    ;------------------------------------------------draw------------------------------------------------

    ;推荐 drawImage
    GdipDrawImageRectRect(pBitmap, aRectTo, aRectFrom:="", Matrix:=1) {
        if !(Matrix ~= "^\d+$")
            ImageAttr := this.GdipSetImageAttributesColorMatrix(Matrix)
        else if (Matrix != 1)
            ImageAttr := this.GdipSetImageAttributesColorMatrix(format("1|0|0|0|0|0|1|0|0|0|0|0|1|0|0|0|0|0|{1}|0|0|0|0|0|1", Matrix))
        return dllcall("gdiplus\GdipDrawImageRectRect", "Ptr",this.ptr, "Ptr",pBitmap
            , "float",aRectTo[1],"float",aRectTo[2],"float",aRectTo[3],"float",aRectTo[4]
            , "float",aRectFrom[1],"float",aRectFrom[2],"float",aRectFrom[3],"float",aRectFrom[4]
            , "uint",2 , "Ptr",ImageAttr, "Ptr",0, "Ptr",0)
    }

    ;GdipDrawImageRectRect 简化，用 oPBitmap 当参数，可省略 aRectFrom(默认是全图)
    drawImage(oPBitmap, aRectTo, aRectFrom:="", Matrix:=1) {
        if (!isobject(aRectFrom))
            aRectFrom := [0,0,oPBitmap.getWidth(),oPBitmap.getHeight()]
        if !(Matrix ~= "^\d+$")
            ImageAttr := this.GdipSetImageAttributesColorMatrix(Matrix)
        else if (Matrix != 1)
            ImageAttr := this.GdipSetImageAttributesColorMatrix(format("1|0|0|0|0|0|1|0|0|0|0|0|1|0|0|0|0|0|{1}|0|0|0|0|0|1", Matrix))
        return dllcall("gdiplus\GdipDrawImageRectRect", "Ptr",this.ptr, "Ptr",oPBitmap.ptr
            , "float",aRectTo[1],"float",aRectTo[2],"float",aRectTo[3],"float",aRectTo[4]
            , "float",aRectFrom[1],"float",aRectFrom[2],"float",aRectFrom[3],"float",aRectFrom[4]
            , "uint",2 , "Ptr",ImageAttr, "Ptr",0, "Ptr",0)
    }

    GdipSetImageAttributesColorMatrix(Matrix) {
        VarSetCapacity(ColourMatrix, 100, 0)
        Matrix := RegExReplace(RegExReplace(Matrix, "^[^\d-\.]+([\d\.])", "$1", , 1), "[^\d-\.]+", "|")
        Matrix := StrSplit(Matrix, "|")
        loop(25) {
            M := (Matrix[A_Index] != "") ? Matrix[A_Index] : Mod(A_Index-1, 6) ? 0 : 1
            NumPut(M, ColourMatrix, (A_Index-1)*4, "float")
        }
        DllCall("gdiplus\GdipCreateImageAttributes", "UPtr*",ImageAttr)
        DllCall("gdiplus\GdipSetImageAttributesColorMatrix", "UPtr",ImageAttr, "int",1, "int",1, "UPtr",&ColourMatrix, "UPtr",0, "int",0)
        return ImageAttr
    }

    GdipDrawRectangle(pPen, aRect) {
        return dllcall("gdiplus\GdipDrawRectangle", "UPtr",this.ptr, "UPtr",pPen, "float",aRect[1],"float",aRect[2],"float",aRect[3],"float",aRect[4])
    }

    ; Pen:		the pen used to draw the line
    ;points		[x1,y1,x2,y2]
    GdipDrawLine(pPen, points) {
        return dllcall("gdiplus\GdipDrawLine", "UPtr",this.ptr, "UPtr",pPen, "float",points.1, "float",points.2, "float",points.3, "float",points.4)
    }

    GdipDrawEllipse(pPen, aRect) {
        return dllcall("gdiplus\GdipDrawEllipse", "UPtr",this.ptr, "UPtr",pPen, "float",aRect[1],"float",aRect[2],"float",aRect[3],"float",aRect[4])
    }

    drawLines(pPen, points) {
        points := points.clone()
        loop(points.length() - 1)
            this.GdipDrawLine(pPen, points), points.RemoveAt(1)
    }

    ; GdipDrawString:	Writes some text with a specified font, rectangle, _stringFormat and Brush on the Graphics
    ; sText:		The text you want to write.
    ; font:		The font you want to use. Has to be a GDIp.font object
    ; rect:		A 4 value array defining [ x, y, w, h ] of the area you want to write to.
    ; stringFormat:	Some options of the text like the text direction. Has to be a GDIp.StringFormat object
    ; brush:		Defines the color of the text. Has to be a GDI+ Brush object. (currently GDIp.SolidBrush & GDIp.LinearGradientBrush)
    ; 参数太多，可能不好用
    GdipDrawString(sText, pFont, pStringFormat, pBrush, aRect) {
        ; hyf_objView(aRect, pFont . "`n" . pStringFormat . "`n" . pBrush)
        VarSetCapacity(rectF, 16, 0)
        for k, entry in aRect
            numput(entry, rectF, k * 4 - 4, "float")
        return dllcall("gdiplus\GdipDrawString", "UPtr",this.ptr, "WStr",sText, "int",-1, "UPtr",pFont, "UPtr",&rectF, "UPtr",pStringFormat, "UPtr",pBrush)
    }

    ; opts := "x10p y60p w80p Centre cff000000 r4 s18p Bold"
    DrawText(sText, opts, sFont:="Arial", width:="", height:="", Measure:=0) {
        arrOpt := StrSplit(opts, " ")
        objStyle := {
            "Regular":0,
            "Bold":1,
            "Italic":2,
            "BoldItalic":3,
            "Underline":4,
            "Strikeout":8,
        }
        objAlign := {
            "Near":0,
            "Left":0,
            "Centre":1,
            "Center":1,
            "Far":2,
            "Right":2,
        }
        objPos := {
            "Top":1,
            "Up":1,
            "Bottom":1,
            "Down":1,
            "vCentre":1,
            "vCenter":1,
        }
        objRes := {}
        style := align := 0
        for _, v in arrOpt {
            if objStyle.haskey(v)
                style |= objStyle[v]
            else if objAlign.haskey(v)
                align |= objAlign[v]
            else if objPos.haskey(v)
                arrPos := [1]
            else { ;处理其他选项
                objType := {
                    "x":0,
                    "y":0,
                    "w":width,
                    "h":height,
                    "c":0xff000000,
                    "r":4,
                    "s":12,
                    "NoWrap":0x4000,
                }
                ;完整匹配
                if (v == "NoWrap") {
                    objType["NoWrap"] := 0x4000 | 0x1000
                    continue
                } else if (v ~= "i)Bottom|Down|vCentre|vCenter") {
                    if (v = "vCentre") || (v = "vCenter")
                        objType["y"] += (height-objType["h"]) // 2
                    else if (v = "Bottom") || (v = "Down")
                        objType["y"] := height - objType["h"]
                }
                ;匹配首字母
                RegExMatch(v, "i)([a-z])([a-f0-9]+)([a-z])?", m)
                try
                    tp := StrLower(m[1])
                catch
                    msgbox(v)
                ; msgbox(m[1] . "`n" . m[2] . "`n" . m[3])
                if (tp == "x")
                    objType[tp] := m[3] ? width*(m[2]/100) : m[2]
                else if (tp == "y")
                    objType[tp] := m[3] ? height*(m[2]/100) : m[2]
                else if (tp == "w")
                    objType[tp] := m[3] ? width*(m[2]/100) : m[2]
                else if (tp == "h")
                    objType[tp] := m[3] ? height*(m[2]/100) : m[2]
                else if (tp == "c")
                    objType[tp] := "0x" . m[2]
                else if (tp == "r")
                    objType[tp] := m[2] ;要求0-5
                else if (tp == "s")
                    objType[tp] := m[2]
            }
        }
        ; pattern_opts := (A_AhkVersion < "2") ? "iO)" : "i)"
        ; RegExMatch(opts, pattern_opts . "X([\-\d\.]+)(p*)", xpos)
        ; RegExMatch(opts, pattern_opts . "Y([\-\d\.]+)(p*)", ypos)
        ; RegExMatch(opts, pattern_opts . "W([\-\d\.]+)(p*)", width)
        ; RegExMatch(opts, pattern_opts . "H([\-\d\.]+)(p*)", height)
        ; RegExMatch(opts, pattern_opts . "C(?!(entre|enter))([a-f\d]+)", Colour)
        ; RegExMatch(opts, pattern_opts . "Top|Up|Bottom|Down|vCentre|vCenter", vPos)
        ; RegExMatch(opts, pattern_opts . "R(\d)", Rendering)
        ; RegExMatch(opts, pattern_opts . "S(\d+)(p*)", Size)
        ; ; if Colour && !GdipDeleteBrush(this.GdipCloneBrush(Colour[2])) {
        ; ;     PassBrush := 1
        ; ;     pBrush := Colour[2]
        ; ; }
        ; if !(width && IHeight) && ((xpos && xpos[2]) || (ypos && ypos[2]) || (width && width[2]) || (height && height[2]) || (Size && Size[2]))
        ;     return -1
        ; style := 0
        ; Styles := "Regular|Bold|Italic|BoldItalic|Underline|Strikeout"
        ; for k, valStyle in StrSplit( Styles, "|" ) {
        ;     if RegExMatch(opts, "\b" . valStyle)
        ;         style |= (valStyle != "StrikeOut") ? (A_Index-1) : 8
        ; }
        ; Align := 0
        ; Alignments := [
        ;     "Near",
        ;     "Left",
        ;     "Centre",
        ;     "Center",
        ;     "Far",
        ;     "Right",
        ; ]
        ; For k, valAlignment in Alignments {
        ;     if (opts ~= "\b" . valAlignment)
        ;         Align |= A_Index//2.1	; 0|0|1|1|2|2
        ; }
        ; xpos := (xpos && (xpos[1] != "")) ? (xpos[2] ? width*(xpos[1]/100) : xpos[1]) : 0
        ; ypos := (ypos && (ypos[1] != "")) ? (ypos[2] ? height*(ypos[1]/100) : ypos[1]) : 0
        ; width := (width && width[1]) ? (width[2] ? width*(width[1]/100) : width[1]) : width
        ; height := (height && height[1]) ? (height[2] ? IHeight*(height[1]/100) : height[1]) : IHeight
        ; Colour := "0x" . (Colour && Colour[2] ? Colour[2] : "ff000000")
        ; ; if !PassBrush
        ; ;     Colour := format("0x{1}", Colour && Colour[2] ? Colour[2] : "ff000000")
        ; Rendering := (Rendering && (Rendering[1] >= 0) && (Rendering[1] <= 5)) ? Rendering[1] : 4
        ; Size := (Size && (Size[1] > 0)) ? (Size[2] ? IHeight*(Size[1]/100) : Size[1]) : 12
        oFont := new GDIP_Font(sFont, objType["s"])
        oStringFormat := new GDIP_StringFormat(objType["NoWrap"])
        oBrush := new GDIP_Brush(objType["c"])
        ; pBrush := PassBrush ? pBrush : Gdip_BrushCreateSolid(Colour)
        ; if !(hFamily && hFont && hFormat && pBrush && this.ptr)
        ;     return !this.ptr ? -2 : !hFamily ? -3 : !hFont ? -4 : !hFormat ? -5 : !pBrush ? -6 : 0
        aRect := [objType["x"], objType["y"], objType["w"], objType["h"]]
        this.GdipSetTextRenderingHint(objType["r"])
        oStringFormat.GdipSetStringFormatAlign(align)
        arrRes := this.GdipMeasureString(sText, oFont.ptr, oStringFormat.ptr, aRect)
        if !Measure
            _E := this.GdipDrawString(sText, oFont.ptr, oStringFormat.ptr, oBrush.ptr, aRect)
        ; if !PassBrush
        ;     Gdip_DeleteBrush(pBrush)
        oBrush := ""
        oStringFormat := ""
        oFont := ""
        return _E ? _E : arrRes
    }

    GdipMeasureString(sText, pFont, pStringFormat, aRect) {
        base.CreateRect(RectF, aRect)
        VarSetCapacity(outRect, 16, 0)
        res := dllcall("gdiplus\GdipMeasureString"
            , "UPtr",this.ptr
            , "UPtr", &sText
            , "int",-1
            , "UPtr",pFont
            , "UPtr",&RectF
            , "UPtr",pStringFormat
            , "UPtr",&outRect
            , "uint*",codePointsFitted
            , "uint*",linesFitted)
        return [
            numget(outRect, 0, "float"),
            numget(outRect, 4, "float"),
            numget(outRect, 8, "float"),
            numget(outRect, 12,"float"),
            codePointsFitted,
            linesFitted,
        ]
    }

    ; GdipDrawBeziers: draw a Bezier Curve onto the graphics with the specified pen and points
    ; pen: 	the pen you want to use to draw on the graphics
    ; points: 	An array of starting and control points of a Bezier line
    ; A single Bezier line consists of 4 points a starting point 2 control points and an end point
    ; The line never actually goes through the control points
    ; The control points control the tangent in the starting and end point and their distance controls how strongly the curve follows there
    GdipDrawBeziers(pPen, points) {
        pointsBuffer := ""
        VarSetCapacity(pointsBuffer,  8 * points.length(), 0)
        for each, point in points
            numput(point.1, pointsBuffer, each * 8 - 8, "float"), numput(point.2, pointsBuffer, each * 8 - 4, "float")
        return dllcall("gdiplus\GdipDrawBeziers", "UPtr",this.ptr, "UPtr",pPen, "UPtr", &pointsBuffer, "uint",points.length())
    }

    ;画箭头：妖提供
    ;=======================================================
    ;
    ;                                            M5
    ;                                            M4
    ; A------------------------------------------M1-d-B
    ;                                            M2
    ;                                            M3
    ;
    ;M1-M2，宽为w1
    ;M1-M3，宽为w2
    ;=======================================================
    drawArrow(pGraphics, pBrush, Ax, Ay, Bx, By) {
        if (Ax=Bx)  ;水平线、垂直线的斜率为0、无斜率，对于作图都有问题，所以人为补了1个像素
            Bx := Ax+1
        if (Ay=By)
            By := Ay+1
        l := sqrt((Ax- Bx)**2 +(Ay - By)**2) ; 起点终点之间的距离
        w1 := 3
        w2 := 5
        ; d:=l/10   ;箭头取1/10长度
        if (l < 50)
            d := 10
        else if (l<100)
            d := 20
        else
            d := 30
        My1 := By-(d*(By-Ay))/l
        Mx1 := Bx-(d*(Bx-Ax))/l
        k := (By-Ay)/(Bx-Ax)   ;斜率
        kk := -1/k             ;垂直线的斜率
        zz := sqrt(kk*kk+1)
        Mx2 := Mx1+d/(w1*zz)
        My2 := My1+kk*d/(w1*zz)
        Mx4 := Mx1-d/(w1*zz)
        My4 := My1-kk*d/(w1*zz)
        Mx3 := Mx1+d/(w2*zz)
        My3 := My1+kk*d/(w2*zz)
        Mx5 := Mx1-d/(w2*zz)
        My5 := My1-kk*d/(w2*zz)
        point :=  format("{1},{2}|{3},{4}|{5},{6}|{7},{8}|{9},{10}|{11},{12}", Ax,Ay,Mx3,My3,Mx2,My2,Bx,By,Mx4,My4,Mx5,My5)
        this.GdipFillPolygon(pBrush, point, FillMode:=1)
    }

    ;------------------------------------------------fill------------------------------------------------

    fillRoundedRectangle(pBrush, aRect, r) {
        x := aRect[1]
        y := aRect[2]
        w := aRect[3]
        h := aRect[4]
        this.GdipGetClip()
        this.GdipSetClipRect([x-r, y-r, 2*r, 2*r], 4)
        this.GdipSetClipRect([x+w-r, y-r, 2*r, 2*r], 4)
        this.GdipSetClipRect([x-r, y+h-r, 2*r, 2*r], 4)
        this.GdipSetClipRect([x+w-r, y+h-r, 2*r, 2*r], 4)
        _E := this.GdipFillRectangle(pBrush, aRect)
        this.GdipSetClipRegion(0)
        this.GdipSetClipRect([x-(2*r), y+r, w+(4*r), h-(2*r)], 4)
        this.GdipSetClipRect([x+r, y-(2*r), w-(2*r), h+(4*r)], 4)
        this.GdipFillEllipse(pBrush, [x, y, 2*r, 2*r])
        this.GdipFillEllipse(pBrush, [x+w-(2*r), y, 2*r, 2*r])
        this.GdipFillEllipse(pBrush, [x, y+h-(2*r), 2*r, 2*r])
        this.GdipFillEllipse(pBrush, [x+w-(2*r), y+h-(2*r), 2*r, 2*r])
        this.GdipSetClipRegion(0)
        this.GdipDeleteRegion()
        return _E
    }

    ; extracted from: https://github.com/tariqporter/Gdip2/blob/master/lib/Object.ahk
    ; and adapted by Marius Șucan
    ; fillRoundedRectangle2(pBrush, aRect, r) {
    ;     x := aRect[1]
    ;     y := aRect[2]
    ;     w := aRect[3]
    ;     h := aRect[4]
    ;     r := (w <= h) ? (r < w // 2) ? r : w // 2 : (r < h // 2) ? r : h // 2
    ;     path1 := this.GdipCreatePath(0)
    ;     this.GdipAddPathRectangle(path1, [x+r, y, w-(2*r), r])
    ;     this.GdipAddPathRectangle(path1, [x+r, y+h-r, w-(2*r), r])
    ;     this.GdipAddPathRectangle(path1, [x, y+r, r, h-(2*r)])
    ;     this.GdipAddPathRectangle(path1, [x+w-r, y+r, r, h-(2*r)])
    ;     this.GdipAddPathRectangle(path1, [x+r, y+r, w-(2*r), h-(2*r)])
    ;     this.GdipAddPathPie(path1, [x, y, 2*r, 2*r], 180, 90)
    ;     this.GdipAddPathPie(path1, [x+w-(2*r), y, 2*r, 2*r], 270, 90)
    ;     this.GdipAddPathPie(path1, [x, y+h-(2*r), 2*r, 2*r], 90, 90)
    ;     this.GdipAddPathPie(path1, [x+w-(2*r), y+h-(2*r), 2*r, 2*r], 0, 90)
    ;     E := this.GdipFillPath(this.ptr, pBrush, path1)
    ;     this.GdipDeletePath(path1)
    ;     return E
    ; }

    GdipFillRectangle(pBrush, aRect) {
        return dllcall("gdiplus\GdipFillRectangle", "Ptr",this.ptr, "Ptr",pBrush, "float",aRect.1, "float",aRect.2, "float",aRect.3, "float",aRect.4)
    }

    GdipFillEllipse(pBrush, aRect) {
        if (!pBrush)
            msgbox(A_ThisHotkey)
        return dllcall("gdiplus\GdipFillEllipse", "Ptr",this.ptr, "Ptr",pBrush, "float",aRect.1, "float",aRect.2, "float",aRect.3, "float",aRect.4)
    }

    GdipFillPolygon(pBrush, points, fillMode:=0) {
        VarSetCapacity(pointBuffer, 8 * points.length(), 0)
        for pointNr, point in points {
            numput(point.1, pointBuffer, pointNr * 8 - 8, "float")
            numput(point.2, pointBuffer, pointNr * 8 - 4, "float")
        }
        return dllcall("gdiplus\GdipFillPolygon", "Ptr",this.ptr, "Ptr",pBrush, "Ptr", &pointBuffer, "int",points.length(), "int",fillMode)
    }

    ; 起始角度(右边为0), 阴影角度
    GdipFillPie(pBrush, aRect, angles) {
        return dllcall("gdiplus\GdipFillPie", "Ptr",this.ptr, "Ptr",pBrush, "float",aRect[1],"float",aRect[2],"float",aRect[3],"float",aRect[4], "float",angles.1, "float",angles.2)
    }

    GdipFillPath(pGraphics, pBrush, pPath) {
        return dllcall("gdiplus\GdipFillPath", "UPtr",pGraphics, "UPtr",pBrush, "UPtr",pPath)
    }

    GdipCreateRegion() {
        dllcall("gdiplus\GdipCreateRegion", "UInt*",region)
        if (!region)
            msgbox(A_ThisFunc)
        return this.region := region
    }
    GdipGetClip() {
        this.GdipCreateRegion()
        dllcall("gdiplus\GdipGetClip", "UPtr",this.ptr, "UInt",this.region)
        return this.region
    }

    GdipSetClipRect(aRect, CombineMode:=0) {
        return dllcall("gdiplus\GdipSetClipRect",  "UPtr",this.ptr, "float",aRect[1],"float",aRect[2],"float",aRect[3],"float",aRect[4], "int",CombineMode)
    }

    GdipSetClipRegion(CombineMode:=0) {
        return dllcall("gdiplus\GdipSetClipRegion", "UPtr",this.ptr, "UPtr",this.region, "int",CombineMode)
    }

    GdipDeleteRegion() {
        return dllcall("gdiplus\GdipDeleteRegion", "UPtr",this.region)
    }

    GdipCreatePath(BrushMode:=0) {
        dllcall("gdiplus\GdipCreatePath", "int",BrushMode, "UPtr*",pPath)
        return pPath
    }

    GdipAddPathRectangle(pPath, aRect) {
        return dllcall("gdiplus\GdipAddPathRectangle",A_PtrSize ? "UPtr" : "UInt",pPath, "float",aRect[1],"float",aRect[2],"float",aRect[3],"float",aRect[4])
    }

    GdipAddPathPie(pPath, aRect, StartAngle, SweepAngle) {
        return dllcall("gdiplus\GdipAddPathPie", "UPtr",pPath, "float",aRect[1],"float",aRect[2],"float",aRect[3],"float",aRect[4], "float",StartAngle, "float",SweepAngle)
    }

    Gdip_AddPathBeziers(pPath, Points) {
        Points := StrSplit(Points, "|")
        VarSetCapacity(PointF, 8*Points.Length())
        for _, Point in Points {
            Coord := StrSplit(Point, ",")
            NumPut(Coord[1], PointF, 8*(A_Index-1), "float")
            NumPut(Coord[2], PointF, (8*(A_Index-1))+4, "float")
        }
        return DllCall("gdiplus\GdipAddPathBeziers", "UPtr", pPath, "UPtr", &PointF, "int", Points.Length())
    }

    ; Adds a Bézier spline to the current figure of this path
    GdipAddPathBezier(pPath, x1, y1, x2, y2, x3, y3, x4, y4) {
        return DllCall("gdiplus\GdipAddPathBezier", "UPtr", pPath
            , "float", x1, "float", y1, "float", x2, "float", y2
            , "float", x3, "float", y3, "float", x4, "float", y4)
    }

    ;#####################################################################################
    ; Function Gdip_AddPathLines
    ; Description Adds a sequence of connected lines to the current figure of this path.
    ;
    ; pPath Pointer to the GraphicsPath
    ; Points the coordinates of all the points passed as x1,y1|x2,y2|x3,y3.....
    ;
    ; return status enumeration. 0 = success
    GdipAddPathLine2(pPath, Points) {
        Points := StrSplit(Points, "|")
        VarSetCapacity(PointF, 8*Points.Length())
        for _, Point in Points {
            Coord := StrSplit(Point, ",")
            NumPut(Coord[1], PointF, 8*(A_Index-1), "float")
            NumPut(Coord[2], PointF, (8*(A_Index-1))+4, "float")
        }
        return DllCall("gdiplus\GdipAddPathLine2", "UPtr", pPath, "UPtr", &PointF, "int", Points0)
    }

    Gdip_AddPathLine(pPath, aRect) {
        return DllCall("gdiplus\GdipAddPathLine", "UPtr", pPath, "float",aRect[1],"float",aRect[2],"float",aRect[3],"float",aRect[4])
    }

    GdipAddPathArc(pPath, aRect, StartAngle, SweepAngle) {
        return DllCall("gdiplus\GdipAddPathArc", "UPtr", pPath, "float",aRect[1],"float",aRect[2],"float",aRect[3],"float",aRect[4], "float", StartAngle, "float", SweepAngle)
    }

    ; Starts a new figure without closing the current figure. Subsequent points added to this path are added to the new figure.
    GdipStartPathFigure(pPath) {
        return DllCall("gdiplus\GdipStartPathFigure", "UPtr", pPath)
    }

    ; Closes the current figure of this path.
    GdipClosePathFigure(pPath) {
        return DllCall("gdiplus\GdipClosePathFigure", "UPtr", pPath)
    }


    ; Replaces this path with curves that enclose the area that is filled when this path is drawn by a specified pen. This method also flattens the path.
    GdipWidenPath(pPath, pPen, Matrix:=0, Flatness:=1) {
        return DllCall("gdiplus\GdipWidenPath", "UPtr", pPath, "uint", pPen, "UPtr", Matrix, "float", Flatness)
    }

    GdipClonePath(pPath) {
        DllCall("gdiplus\GdipClonePath", "UPtr",pPath, "UPtr*",pPathClone)
        return pPathClone
    }

    ;#####################################################################################
    ; Function Gdip_DrawPath
    ; Description draws a sequence of lines and curves defined by a GraphicsPath object
    ; pGraphics Pointer to the Graphics of a bitmap
    ; pPen Pointer to a pen
    ; pPath Pointer to a Path
    ; return status enumeration. 0 = success
    GdipDrawPath(pPen, pPath) {
        return DllCall("gdiplus\GdipDrawPath", "UPtr",this.ptr, "UPtr",pPen, "UPtr",pPath)
    }

    GdipDeletePath(pPath) {
        return dllcall("gdiplus\GdipDeletePath", "UPtr",pPath)
    }

    ;oDC 相关方法

    ; DCX_CACHE = 0x2
    ; DCX_CLIPCHILDREN = 0x8
    ; DCX_CLIPSIBLINGS = 0x10
    ; DCX_EXCLUDERGN = 0x40
    ; DCX_EXCLUDEUPDATE = 0x100
    ; DCX_INTERSECTRGN = 0x80
    ; DCX_INTERSECTUPDATE = 0x200
    ; DCX_LOCKWINDOWUPDATE = 0x400
    ; DCX_NORECOMPUTE = 0x100000
    ; DCX_NORESETATTRS = 0x4
    ; DCX_PARENTCLIP = 0x20
    ; DCX_VALIDATE = 0x200000
    ; DCX_WINDOW = 0x1
    ; getEx(hwnd, flags:=0, hrgnClip:=0) {
    ;     Ptr := A_PtrSize ? "UPtr" : "uint"
    ;     return this.hDC := dllcall("GetDCEx",ptr,hwnd, ptr,hrgnClip, "int",flags)
    ; }

    release(hwnd:=0) {
        return dllcall("ReleaseDC", "uptr",hwnd, "ptr",this.hDC)
    }

    ;TODO
    ; Raster
    ;   SRCCOPY			= 0x00CC0020
    ;   BLACKNESS		= 0x00000042
    ;   NOTSRCERASE		= 0x001100A6
    ;   NOTSRCCOPY		= 0x00330008
    ;   SRCERASE		= 0x00440328
    ;   DSTINVERT		= 0x00550009
    ;   PATINVERT		= 0x005A0049
    ;   SRCINVERT		= 0x00660046
    ;   SRCAND			= 0x008800C6
    ;   MERGEPAINT		= 0x00BB0226
    ;   MERGECOPY		= 0x00C000CA
    ;   SRCPAINT		= 0x00EE0086
    ;   PATCOPY			= 0x00F00021
    ;   PATPAINT		= 0x00FB0A09
    ;   WHITENESS		= 0x00FF0062
    ;   CAPTUREBLT		= 0x40000000
    ;   NOMIRRORBITMAP		= 0x80000000
    BitBlt(aRectTo, sDC, sx, sy, Raster:=0x00CC0020) {
        if (!this.hDC || !sDC)
            msgbox(A_ThisFunc)
        return dllcall("gdi32\BitBlt", "UPtr",this.hDC
            , "int",aRectTo[1],"int",aRectTo[2],"int",aRectTo[3],"int",aRectTo[4]
            , "UPtr",sDC, "int",sx, "int",sy, "uint",Raster)
    }

    SelectObject(hGdiObj:=0) {
        if (hGdiObj) {
            if !this.pSelectSave ;只记录一次
                return this.pSelectSave := dllcall("SelectObject", "UPtr",this.hDC, "UPtr",hGdiObj)
            else
                return dllcall("SelectObject", "UPtr",this.hDC, "UPtr",hGdiObj)
        } else ;还原
            return dllcall("SelectObject", "UPtr",this.hDC, "UPtr",this.pSelectSave)
    }

    ;aRect =屏幕上显示的位置大小
    UpdateLayeredWindow(hwnd, aRect, alpha:=255) {
        if (!hwnd || !this.hDC)
            msgbox(A_ThisFunc)
        x := aRect[1]
        y := aRect[2]
        w := aRect[3]
        h := aRect[4]
        VarSetCapacity(pt, 8, 0)
        numput(x, pt, 0, "uint")
        numput(y, pt, 4, "uint")
        return dllcall("UpdateLayeredWindow"
            , "uptr", hwnd
            , "uptr", 0
            , "uptr", &pt
            , "int64*", w|h<<32
            , "uptr", this.hDC
            , "int64*", 0
            , "uint", 0
            , "uint*", alpha<<16|1<<24
            , "uint", 2)
    }

    showByGui() {
        oGui := GuiCreate("-Caption +E0x80000 +LastFound +AlwaysOnTop +ToolWindow +OwnDialogs")
        oGui.Show("NA")
        this.UpdateLayeredWindow(oGui.hwnd, [0, 0, A_ScreenWidth, A_ScreenHeight])
    }

}

;pp
class GDIP_Pen extends _GDIP {
    ptr := 0

    __new(argbOrBrush, width) {
        if isobject(argbOrBrush)
            this.createByBrush(argbOrBrush)
        else
            this.createByArgb(argbOrBrush, width)
        ; base.registerObject(this)
    }

    __delete() {
        ; msgbox(A_ThisFunc . '---')
        dllcall("gdiplus\GdipDeletePen", "UPtr",this.ptr)
    }

    createByArgb(argb, width) {
        ;TODO 是否需要先删除原画笔？
        if this.ptr
            this.__delete()
        res := dllcall("gdiplus\GdipCreatePen1", "uint",argb, "float",width, "int",2, "UPtr*",pPen)
        if (!pPen)
            msgbox(A_ThisFunc . "`n" . argb . "`n" . width)
        this.ptr := pPen
        return res
    }

    createByBrush(oBrush){
        if this.ptr
            this.__delete()
        this.pBrush := oBrush ;TODO 是否需要保存 <2020-12-11 14:36:59> hyaray
        res := dllcall("gdiplus\GdipCreatePen2", "UPtr",argbOrBrush, "float",width, "int",2, "UPtr*",pPen)
        this.ptr := pPen
        return res
    }

    GdipGetPenWidth() {
        dllcall("gdiplus\GdipGetPenWidth", "UPtr",this.ptr, "float*",width)
        return width
    }
    GdipSetPenWidth(width) {
        return dllcall("gdiplus\GdipSetPenWidth", "UPtr",this.ptr, "float",width)
    }

    GdipGetPenColor() {
        dllcall("gdiplus\GdipGetPenColor", "UPtr",this.ptr, "uint*",color)
        return color
    }
    GdipSetPenColor(color) {
        return dllcall("gdiplus\GdipSetPenColor", "UPtr",this.ptr, "uint",color)
    }

    ; getBrush() {
    ;     return this.ptr
    ; }
    ; setBrush(pBrush := "") {
    ;     if (this.haskey("pBrush") && pBrush)
    ;         this.ptr := pBrush
    ;     return dllcall("gdiplus\GdipSetPenBrushFill", "UPtr",this.ptr, "UPtr",this.ptr.getpBrush())
    ; }

}

;bb
class GDIP_Brush extends _GDIP {
    ptr := 0
    __new(argb, argbBack:=0, HatchStyle:=0) {
        if argbBack
            this.GdipCreateHatchBrush(argb, argbBack, HatchStyle)
        else
            this.GdipCreateSolidFill(argb)
    }

    __delete() {
        ; msgbox(A_ThisFunc . '---')
        dllcall("gdiplus\GdipDeleteBrush", "UPtr",this.ptr)
    }

    GdipCreateSolidFill(argb) {
        if !argb
            msgbox(A_ThisFunc . "`nargb = 0")
        ;TODO 是否需要先删除原画刷？
        if this.ptr
            this.__delete()
        dllcall("gdiplus\GdipCreateSolidFill", "uint",argb, "UPtr*",pBrush)
        if (!pBrush)
            msgbox(A_ThisFunc)
        this.ptr := pBrush
    }

    ; LinearGradientModeHorizontal = 0
    ; LinearGradientModeVertical = 1
    ; LinearGradientModeForwardDiagonal = 2
    ; LinearGradientModeBackwardDiagonal = 3
    GdipCreateLineBrushFromRect(aRect, ARGB1, ARGB2, LinearGradientMode:=1, WrapMode:=1) {
        this.CreateRect(Rect, aRect)
        dllcall("gdiplus\GdipCreateLineBrushFromRect", "UPtr",&Rect, "int",ARGB1, "int",ARGB2, "int",LinearGradientMode, "int",WrapMode, "UPtr*",LGpBrush)
        return this.ptr := LGpBrush
    }

    ; HatchStyleHorizontal = 0
    ; HatchStyleVertical = 1
    ; HatchStyleForwardDiagonal = 2
    ; HatchStyleBackwardDiagonal = 3
    ; HatchStyleCross = 4
    ; HatchStyleDiagonalCross = 5
    ; HatchStyle05Percent = 6
    ; HatchStyle10Percent = 7
    ; HatchStyle20Percent = 8
    ; HatchStyle25Percent = 9
    ; HatchStyle30Percent = 10
    ; HatchStyle40Percent = 11
    ; HatchStyle50Percent = 12
    ; HatchStyle60Percent = 13
    ; HatchStyle70Percent = 14
    ; HatchStyle75Percent = 15
    ; HatchStyle80Percent = 16
    ; HatchStyle90Percent = 17
    ; HatchStyleLightDownwardDiagonal = 18
    ; HatchStyleLightUpwardDiagonal = 19
    ; HatchStyleDarkDownwardDiagonal = 20
    ; HatchStyleDarkUpwardDiagonal = 21
    ; HatchStyleWideDownwardDiagonal = 22
    ; HatchStyleWideUpwardDiagonal = 23
    ; HatchStyleLightVertical = 24
    ; HatchStyleLightHorizontal = 25
    ; HatchStyleNarrowVertical = 26
    ; HatchStyleNarrowHorizontal = 27
    ; HatchStyleDarkVertical = 28
    ; HatchStyleDarkHorizontal = 29
    ; HatchStyleDashedDownwardDiagonal = 30
    ; HatchStyleDashedUpwardDiagonal = 31
    ; HatchStyleDashedHorizontal = 32
    ; HatchStyleDashedVertical = 33
    ; HatchStyleSmallConfetti = 34
    ; HatchStyleLargeConfetti = 35
    ; HatchStyleZigZag = 36
    ; HatchStyleWave = 37
    ; HatchStyleDiagonalBrick = 38
    ; HatchStyleHorizontalBrick = 39
    ; HatchStyleWeave = 40
    ; HatchStylePlaid = 41
    ; HatchStyleDivot = 42
    ; HatchStyleDottedGrid = 43
    ; HatchStyleDottedDiamond = 44
    ; HatchStyleShingle = 45
    ; HatchStyleTrellis = 46
    ; HatchStyleSphere = 47
    ; HatchStyleSmallGrid = 48
    ; HatchStyleSmallCheckerBoard = 49
    ; HatchStyleLargeCheckerBoard = 50
    ; HatchStyleOutlinedDiamond = 51
    ; HatchStyleSolidDiamond = 52
    ; HatchStyleTotal = 53
    ; https://docs.microsoft.com/en-us/windows/win32/api/gdiplusenums/ne-gdiplusenums-hatchstyle
    GdipCreateHatchBrush(argbFront, argbBack, HatchStyle:=0) {
        dllcall("gdiplus\GdipCreateHatchBrush", "int",HatchStyle, "UInt",argbFront, "UInt",argbBack, "UPtr*",pBrush)
        if (!pBrush)
            msgbox(A_ThisFunc)
        return this.ptr := pBrush
    }

    GdipCloneBrush() {
        dllcall("gdiplus\GdipCloneBrush", "UPtr",this.ptr, "UPtr*",pBrushClone)
        return pBrushClone
    }

    SetColor(argb) {
        dllcall("gdiplus\GdipSetSolidFillColor", "UPtr",this.ptr, "uint",argb)
    }


    getColor() {
        dllcall("gdiplus\GdipSetSolidFillColor", "UPtr",this.ptr, "uint*",argb)
        return argb
    }

}

;ff
class GDIP_Font extends _GDIP {
    ptr := 0

    ;oInstance
    ;   oDC
    ;   oFontFamily
    ;   sFont
    __new(oInstance:="Arial", size:=12) {
        if isobject(oInstance) {
            if (oInstance.__class == "GDIP_Graphics")
                res := dllcall("gdiplus\GdipCreateFontFromDC", "UPtr",oInstance.hDC, "UPtr*",pFont)
            else if (oInstance.__class == "GDIP_FontFamily")
                this.GdipCreateFont(oInstance.ptr, size)
        } else { ;字体名称
            oFontFamily := new GDIP_FontFamily(oInstance)
            this.GdipCreateFont(oFontFamily.ptr, size)
            oFontFamily := ""
        }
        if res
            msgbox(A_ThisFunc)
    }

    __delete() {
        dllcall("gdiplus\GdipDeleteFont", "UPtr",this.ptr)
    }

    ; Regular = 0
    ; Bold = 1
    ; Italic = 2
    ; BoldItalic = 3
    ; Underline = 4
    ; Strikeout = 8
    GdipCreateFont(pFontFamily, size, style:=0) {
        res := dllcall("gdiplus\GdipCreateFont", "UPtr",pFontFamily, "float",size, "uint",style, "uint",0, "UPtr*",pFont)
        if res
            msgbox(A_ThisFunc)
        return this.ptr := pFont
    }
}

class GDIP_FontFamily {
    __new(sFont) {
        ; res := dllcall("gdiplus\GdipCreateFontFamilyFromName" , "UPtr",&sFont , "uint",0 , "UPtr*",pFontFamily)
        res := dllcall("gdiplus\GdipCreateFontFamilyFromName", "WStr",sFont, "UPtr",0, "UPtr*",pFontFamily )
        if !pFontFamily {
            msgbox(format("字体{1}不存在", sFont),,0x40000)
            exit
        }
        this.ptr := pFontFamily
    }

    __delete() {
        dllcall( "gdiplus\GdipDeleteFontFamily", "UPtr",this.ptr )
    }

}

;ss
;文字在框中的对齐方式
class GDIP_StringFormat extends _GDIP {

    /*
    formatFlags: Defines some settings of the _StringFormat object
    typedef enum  {
        StringFormatFlagsDirectionRightToLeft    = 0x0001,
        StringFormatFlagsDirectionVertical       = 0x0002,
        StringFormatFlagsNoFitBlackBox           = 0x0004,
        StringFormatFlagsDisplayFormatControl    = 0x0020,
        StringFormatFlagsNoFontFallback          = 0x0400,
        StringFormatFlagsMeasureTrailingSpaces   = 0x0800,
        StringFormatFlagsNoWrap                  = 0x1000,
        StringFormatFlagsLineLimit               = 0x2000,
        StringFormatFlagsNoClip                  = 0x4000
    } StringFormatFlags;
    langId: Defines the language this _StringFormat object should use.
    I don't actually know any besides 0 which is LANG_NEUTRAL and represents the users language - further research is necessary.
    */

    __new(formatFlags:=0, langId:=0) {
        this.GdipCreateStringFormat(formatFlags, langId)
    }

    __delete() {
        dllcall("gdiplus\GdipDeleteStringFormat", "UPtr",this.ptr)
    }

    GdipCreateStringFormat(formatFlags, langId) {
        res := dllcall("gdiplus\GdipCreateStringFormat", "uint",formatFlags, "UShort",langId, "UPtr*",pStringFormat)
        if res
            msgbox(A_ThisFunc)
        this.ptr := pStringFormat
    }

    ; Near = 0
    ; Center = 1
    ; Far = 2
    GdipSetStringFormatAlign(align:=1) {
        return dllcall("gdiplus\GdipSetStringFormatAlign", "UPtr",this.ptr, "int",align)
    }

}